package evaluator

import (
	"fmt"
	"monkey/ast"
	"monkey/object"
)

var (
	/*次遇到true或false时都创建一个新的object.Boolean，
	是不是有点烦琐？所有的true都是相同的，false也是如此。
	那为什么每次还要创建新的实例呢？布尔值只有true和false两种可能性
	，所以可以使用true和false的引用来代替每次都新建实例。*/
	TRUE = &object.Boolean{Value:true}
	FALSE = &object.Boolean{Value:false}
	NULL = &object.Null{}
)

func Eval(node ast.Node,env *object.Environment) object.Object{
	switch node := node.(type){
	case *ast.Program:
		return evalProgram(node,env)
	case *ast.ExpressionStatement:
		return Eval(node.Expression,env)
	case *ast.PrefixExpression:
		right := Eval(node.Right,env)
		if isError(right){
			return right
		}
		return evalPrefixExpression(node.Operator,right)
	case *ast.InfixExpression:
		left := Eval(node.Left,env)
		if isError(left){
			return left
		}
		right := Eval(node.Right,env)
		if isError(right){
			return right
		}
		return evalInfixExpression(node.Operator,left,right)
	case *ast.BlockStatement:
		return evalBlockStatement(node,env)
	case *ast.IfExpression:
		return evalIfExpression(node,env)
	case *ast.ReturnStatement:
		val := Eval(node.ReturnValue,env)
		if isError(val){
			return val
		}
		return &object.ReturnValue{Value:val}
	case *ast.LetStatement:
		val := Eval(node.Value,env)
		if isError(val){
			return val
		}
		env.Set(node.Name.Value,val)

		//expression
	case *ast.FunctionLiteral:
		params := node.Parameters
		body := node.Body
		return &object.Function{Parameters: params,Env:env,Body: body}
	case *ast.Identifier:
		return evalIdentifier(node,env)
	case *ast.CallExpression:
		if node.Function.TokenLiteral() == "quote"{
			return quote(node.Arguments[0],env)
		} 
		function := Eval(node.Function,env)
		if isError(function){
			return function
		}
		args := evalExpressions(node.Arguments,env)
		if len(args) == 1 && isError(args[0]){
			return args[0]
		}
		return applyFunction(function,args)
	case *ast.IntegerLiteral:
		return &object.Integer{Value: node.Value}
	case *ast.Boolean:
		return nativeBoolToBooleanObject(node.Value)
		//&object.Boolean{Value: node.Value}
		//nativeBoolToBooleanObject(node.Value)
	case *ast.StringLiteral:
		return &object.String{Value:node.Value}
	case *ast.ArrayLiteral:
		elements := evalExpressions(node.Elements,env)
		if len(elements) == 1 && isError(elements[0]){
			return elements[0]
		}
		return &object.Array{Elements:elements}
	case *ast.IndexExpression:
		left := Eval(node.Left,env)
		if isError(left){
			return left
		}
		index := Eval(node.Index,env)
		if isError(index){
			return index
		}
		return evalIndexExpression(left,index)
	case *ast.HashLiteral:
		return evalHashLiteral(node,env)
	}
	return nil
}

func evalProgram(program *ast.Program,env *object.Environment) object.Object{
	var result object.Object
	for _,statement := range program.Statements{
		result = Eval(statement,env)
		switch result := result.(type){
		case *object.ReturnValue:
			return result.Value
		case *object.Error:
			return result
		}
	}
	return result
}

func evalStatements(stmts []ast.Statement,env *object.Environment) object.Object{
	var result object.Object
	for _,statement := range stmts{
		result = Eval(statement,env)
		if returnValue,ok := result.(*object.ReturnValue);ok{
			return returnValue.Value
		}
	}
	return result
}

func nativeBoolToBooleanObject(input bool) *object.Boolean{
	if input{
		return TRUE
	}
	return FALSE
}

func evalPrefixExpression(operator string,right object.Object)object.Object{
	switch operator{
	case "!":
		return evalBangOperatorExpression(right)
	case "-":
		return evalMinusPrefixOperatorExpression(right)
	default:
		return newError("unknown operator:%s%s",operator,right.Type())
	}
}

func evalBangOperatorExpression(right object.Object)object.Object{
	switch right{
	case TRUE:
		return FALSE
	case FALSE:
		return TRUE
	case NULL:
		return TRUE
	default:
		return FALSE
	}
}

func evalMinusPrefixOperatorExpression(right object.Object)object.Object{
	if right.Type() != object.INTEGER_OBJ{
		return newError("unknown operator: -%s",right.Type())
	}

	value := right.(*object.Integer).Value
	return &object.Integer{Value: -value}
}

func evalInfixExpression(op string,left,right object.Object)object.Object{
	switch{
	case left.Type() == object.INTEGER_OBJ && right.Type() == object.INTEGER_OBJ:
		return evalIntegerInfixExpression(op,left,right)
	case op == "==":
		return nativeBoolToBooleanObject(left==right)
	case op == "!=":
		return nativeBoolToBooleanObject(left!=right)
	case left.Type() != right.Type():
		return newError("type mismatch: %s %s %s",left.Type(),op,right.Type())
	case left.Type() == object.STRING_OBJ && right.Type() == object.STRING_OBJ:
			return evalStringInfixExpression(op,left,right) 
	default:
		return newError("unknown operator: %s %s %s",left.Type(),op,right.Type())
	}
}

func evalIntegerInfixExpression(op string,left,right object.Object)object.Object{
	leftVal := left.(*object.Integer).Value
	rightVal := right.(*object.Integer).Value

	switch op{
	case "+":
		return &object.Integer{Value:leftVal+rightVal}
	case "-":
		return &object.Integer{Value:leftVal-rightVal}
	case "*":
		return &object.Integer{Value:leftVal*rightVal}
	case "/":
		return &object.Integer{Value:leftVal/rightVal}
	case "<":
		return nativeBoolToBooleanObject(leftVal<rightVal)
	case ">":
		return nativeBoolToBooleanObject(leftVal>rightVal)
	case "==":
		return nativeBoolToBooleanObject(leftVal==rightVal)
	case "!=":
		return nativeBoolToBooleanObject(leftVal!=rightVal)
	default:
		return newError("unknown operator: %s %s %s",left.Type(),op,right.Type())
	}
}

func evalIfExpression(ie *ast.IfExpression,env *object.Environment)object.Object{
	condition := Eval(ie.Condition,env)
	if isError(condition){
		return condition
	}
	if isTruthy(condition){
		return Eval(ie.Consequence,env)
	}else if ie.Alternative != nil{
		return Eval(ie.Alternative,env)
	}else{
		return NULL
	}
}

func evalIdentifier(node *ast.Identifier,env *object.Environment)object.Object{
	val,ok := env.Get(node.Value)
	if ok{
		return val
	}
	if builtin,ok := builtins[node.Value];ok{
		return builtin
	}
	return newError("identifier not found: "+node.Value)
}

func isTruthy(obj object.Object) bool{
	switch obj{
	case NULL:
		return false
	case TRUE:
		return true
	case FALSE:
		return false
	default:
		return true
	}
}

func evalBlockStatement(block *ast.BlockStatement,env *object.Environment) object.Object{
	var result object.Object
	for _,statement := range block.Statements{
		result = Eval(statement,env)
		if result != nil{
			rt := result.Type()
			if rt == object.RETURN_VALUE_OBJ || rt == object.ERROR_OBJ{
				return result
			}
		}
	}
	return result
}

func newError(format string,a ...interface{}) *object.Error{
	return &object.Error{Message: fmt.Sprintf(format,a...)}
}

func isError(obj object.Object) bool{
	if obj != nil{
		return obj.Type() == object.ERROR_OBJ
	}
	return false
}

func evalExpressions(exps []ast.Expression,env *object.Environment)[]object.Object{
	var result []object.Object
	for _,e := range exps{
		evaluated := Eval(e,env)
		if isError(evaluated){
			return []object.Object{evaluated}
		}
		result = append(result, evaluated)
	}
	return result
}

func applyFunction(fn object.Object,args []object.Object)object.Object{
	switch fn := fn.(type){
	case *object.Function:
		extendedEnv := extendFunctionEnv(fn,args)
		evaluated := Eval(fn.Body,extendedEnv)
		return unwrapReturnValue(evaluated)
	case *object.Builtin:
		return fn.Fn(args...)
	default:
		return newError("not a function: %s",fn.Type())
	}
	
/*新的applyFunction函数不仅会检查是否真的有一个*object.Function，
还会将fn参数转换为一个*object.Function引用，以便访问该函数的Env字段和Body字段
，而最初的object.Object中并未定义这两个字段。*/
}

func extendFunctionEnv(fn *object.Function,args []object.Object)*object.Environment{
	env := object.NewEnclosedEnvironment(fn.Env)
	for paramIdx,param := range fn.Parameters{
		env.Set(param.Value,args[paramIdx])
	}
	return env
/*extendFunctionEnv函数会创建一个新的*object.Environment，
该环境位于函数环境内部。在这个被包裹的新环境中，函数调用的实参绑定到了函数的形参名称中*/
}

func unwrapReturnValue(obj object.Object)object.Object{
	if returnValue,ok := obj.(*object.ReturnValue);ok{
		return returnValue.Value
	}
	return obj
/*这个被包裹的新环境就是对函数体求值时需要用到的环境。
如果其最后的求值结果是*object.ReturnValue，则必须进行解包*/
}

func evalStringInfixExpression(op string,left,right object.Object)object.Object{
	if op != "+"{
		return newError("unknown operator: %s %s %s",left.Type(),op,right.Type())
	}
	leftVal := left.(*object.String).Value
	rightVal := right.(*object.String).Value
	return &object.String{Value: leftVal + rightVal}
}

func evalIndexExpression(left,index object.Object) object.Object{
	switch{
	case left.Type() == object.ARRAY_OBJ && index.Type() == object.INTEGER_OBJ:
		return evalArrayIndexExpression(left,index)
	case left.Type() == object.HASH_OBJ:
		return evalHashIndexExpression(left,index)
	default:
		return newError("index operator not supported: %s",left.Type())
	}
}

func evalArrayIndexExpression(array,index object.Object) object.Object{
	arrayObject := array.(*object.Array)
	idx := index.(*object.Integer).Value
	max := int64(len(arrayObject.Elements)-1)

	if idx < 0 || idx >max{
		return NULL
	}

	return arrayObject.Elements[idx]
}

func evalHashLiteral(node *ast.HashLiteral,env *object.Environment)object.Object{
	pairs := make(map[object.HashKey]object.HashPair)

	for KeyNode,ValNode := range node.Pairs{
		key := Eval(KeyNode,env)
		if isError(key){
			return key
		}
		hashKey,ok := key.(object.Hashable)
		if !ok{
			return newError("unusable as hash key: %s",key.Type())
		}
		value := Eval(ValNode,env)
		if isError(value){
			return value
		}
		hashed := hashKey.HashKey()
		pairs[hashed] = object.HashPair{Key:key,Value: value}
	}
	return &object.Hash{Pairs: pairs}
}

func evalHashIndexExpression(hash,index object.Object)object.Object{
	hashObject := hash.(*object.Hash)
	key,ok := index.(object.Hashable)
	if !ok{
		return newError("unusable as hash key: %s",index.Type())
	}
	pair,ok := hashObject.Pairs[key.HashKey()]
	if !ok{
		return NULL
	}
	return pair.Value
}